/*
 * $Id: JXComboBox.java 4158 2012-02-03 18:29:40Z kschaefe $
 * 
 * Copyright 2010 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import javax.accessibility.Accessible;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.ComboPopup;

import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.CompoundHighlighter;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.plaf.UIDependent;
import org.jdesktop.swingx.renderer.DefaultListRenderer;
import org.jdesktop.swingx.renderer.JRendererPanel;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.rollover.RolloverRenderer;
import org.jdesktop.swingx.sort.StringValueRegistry;
import org.jdesktop.swingx.util.Contract;

/**
 * An enhanced {@code JComboBox} that provides the following additional
 * functionality:
 * <p>
 * Auto-starts edits correctly for AutoCompletion when inside a {@code JTable}.
 * A normal {@code JComboBox} fails to recognize the first key stroke when it
 * has been
 * {@link org.jdesktop.swingx.autocomplete.AutoCompleteDecorator#decorate(JComboBox)
 * decorated}.
 * <p>
 * Adds highlighting support.
 * 
 * @author Karl Schaefer
 * @author Jeanette Winzenburg
 */
@SuppressWarnings({ "nls", "serial" })
public class JXComboBox extends JComboBox {
	/**
	 * A decorator for the original ListCellRenderer. Needed to hook
	 * highlighters after messaging the delegate.
	 * <p>
	 */
	public class DelegatingRenderer implements ListCellRenderer, RolloverRenderer, UIDependent {
		/** the delegate. */
		private ListCellRenderer delegateRenderer;
		private JRendererPanel wrapper;

		/**
		 * Instantiates a DelegatingRenderer with combo box's default renderer
		 * as delegate.
		 */
		public DelegatingRenderer() {
			this(null);
		}

		/**
		 * Instantiates a DelegatingRenderer with the given delegate. If the
		 * delegate is {@code null}, the default is created via the combo box's
		 * factory method.
		 * 
		 * @param delegate
		 *            the delegate to use, if {@code null} the combo box's
		 *            default is created and used.
		 */
		public DelegatingRenderer(ListCellRenderer delegate) {
			wrapper = new JRendererPanel(new BorderLayout());
			setDelegateRenderer(delegate);
		}

		/**
		 * Sets the delegate. If the delegate is {@code null}, the default is
		 * created via the combo box's factory method.
		 * 
		 * @param delegate
		 *            the delegate to use, if null the list's default is created
		 *            and used.
		 */
		public void setDelegateRenderer(ListCellRenderer delegate) {
			if (delegate == null) {
				delegate = createDefaultCellRenderer();
			}
			delegateRenderer = delegate;
		}

		/**
		 * Returns the delegate.
		 * 
		 * @return the delegate renderer used by this renderer, guaranteed to
		 *         not-null.
		 */
		public ListCellRenderer getDelegateRenderer() {
			return delegateRenderer;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void updateUI() {
			wrapper.updateUI();

			if (delegateRenderer instanceof UIDependent) {
				((UIDependent) delegateRenderer).updateUI();
			} else if (delegateRenderer instanceof Component) {
				SwingUtilities.updateComponentTreeUI((Component) delegateRenderer);
			} else if (delegateRenderer != null) {
				try {
					Component comp = delegateRenderer.getListCellRendererComponent(getPopupListFor(JXComboBox.this), null, -1,
							false, false);
					SwingUtilities.updateComponentTreeUI(comp);
				} catch (Exception e) {
					// nothing to do - renderer barked on off-range row
				}
			}
		}

		// --------- implement ListCellRenderer
		/**
		 * {@inheritDoc}
		 * <p>
		 * 
		 * Overridden to apply the highlighters, if any, after calling the
		 * delegate. The decorators are not applied if the row is invalid.
		 */
		@Override
		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
				boolean cellHasFocus) {
			Component comp = null;

			if (index == -1) {
				comp = delegateRenderer.getListCellRendererComponent(list, value, getSelectedIndex(), isSelected, cellHasFocus);

				if (isUseHighlightersForCurrentValue() && compoundHighlighter != null && getSelectedIndex() != -1) {
					comp = compoundHighlighter.highlight(comp, getComponentAdapter(getSelectedIndex()));

					// this is done to "trick" BasicComboBoxUI.paintCurrentValue
					// which resets all of
					// the painted information after asking the list to render
					// the value. the panel
					// wrappers receives all of the post-rendering
					// configuration, which is dutifully
					// ignored by the real rendering component
					wrapper.add(comp);
					comp = wrapper;
				}
			} else {
				comp = delegateRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

				if ((compoundHighlighter != null) && (index >= 0) && (index < getItemCount())) {
					comp = compoundHighlighter.highlight(comp, getComponentAdapter(index));
				}
			}

			return comp;
		}

		// implement RolloverRenderer

		/**
		 * {@inheritDoc}
		 * 
		 */
		@Override
		public boolean isEnabled() {
			return (delegateRenderer instanceof RolloverRenderer) && ((RolloverRenderer) delegateRenderer).isEnabled();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void doClick() {
			if (isEnabled()) {
				((RolloverRenderer) delegateRenderer).doClick();
			}
		}
	}

	@SuppressWarnings("hiding")
	protected static class ComboBoxAdapter extends ComponentAdapter {
		private final JXComboBox comboBox;

		/**
		 * Constructs a <code>ListAdapter</code> for the specified target
		 * JXList.
		 * 
		 * @param component
		 *            the target list.
		 */
		public ComboBoxAdapter(JXComboBox component) {
			super(component);
			comboBox = component;
		}

		/**
		 * Typesafe accessor for the target component.
		 * 
		 * @return the target component as a
		 *         {@link org.jdesktop.swingx.JXComboBox}
		 */
		public JXComboBox getComboBox() {
			return comboBox;
		}

		/**
		 * A safe way to access the combo box's popup visibility.
		 * 
		 * @return {@code true} if the popup is visible; {@code false} otherwise
		 */
		protected boolean isPopupVisible() {
			if (comboBox.updatingUI) {
				return false;
			}

			return comboBox.isPopupVisible();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean hasFocus() {
			if (isPopupVisible()) {
				JList list = getPopupListFor(comboBox);

				return list != null && list.isFocusOwner() && (row == list.getLeadSelectionIndex());
			}

			return comboBox.isFocusOwner();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public int getRowCount() {
			return comboBox.getModel().getSize();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Object getValueAt(int row, int column) {
			return comboBox.getModel().getElementAt(row);
		}

		/**
		 * {@inheritDoc} This is implemented to query the table's
		 * StringValueRegistry for an appropriate StringValue and use that for
		 * getting the string representation.
		 */
		@Override
		public String getStringAt(int row, int column) {
			StringValue sv = comboBox.getStringValueRegistry().getStringValue(row, column);

			return sv.getString(getValueAt(row, column));
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Rectangle getCellBounds() {
			JList list = getPopupListFor(comboBox);

			if (list == null) {
				assert false;
				return new Rectangle(comboBox.getSize());
			}

			return list.getCellBounds(row, row);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean isCellEditable(int row, int column) {
			return row == -1 && comboBox.isEditable();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean isEditable() {
			return isCellEditable(row, column);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean isSelected() {
			if (isPopupVisible()) {
				JList list = getPopupListFor(comboBox);

				return list != null && row == list.getLeadSelectionIndex();
			}

			return comboBox.isFocusOwner();
		}
	}

	class StringValueKeySelectionManager implements KeySelectionManager, Serializable, UIDependent {
		private long timeFactor;
		private long lastTime = 0L;
		private String prefix = "";
		private String typedString = "";

		public StringValueKeySelectionManager() {
			updateUI();
		}

		@Override
		public int selectionForKey(char aKey, ComboBoxModel aModel) {
			if (lastTime == 0L) {
				prefix = "";
				typedString = "";
			}

			int startIndex = getSelectedIndex();

			if (EventQueue.getMostRecentEventTime() - lastTime < timeFactor) {
				typedString += aKey;
				if ((prefix.length() == 1) && (aKey == prefix.charAt(0))) {
					// Subsequent same key presses move the keyboard focus to
					// the next
					// object that starts with the same letter.
					startIndex++;
				} else {
					prefix = typedString;
				}
			} else {
				startIndex++;
				typedString = "" + aKey;
				prefix = typedString;
			}

			lastTime = EventQueue.getMostRecentEventTime();

			if (startIndex < 0 || startIndex >= aModel.getSize()) {
				startIndex = 0;
			}

			for (int i = startIndex, c = aModel.getSize(); i < c; i++) {
				String v = getStringAt(i).toLowerCase();

				if (v.length() > 0 && v.charAt(0) == aKey) {
					return i;
				}
			}

			for (int i = startIndex, c = aModel.getSize(); i < c; i++) {
				String v = getStringAt(i).toLowerCase();

				if (v.length() > 0 && v.charAt(0) == aKey) {
					return i;
				}
			}

			for (int i = 0; i < startIndex; i++) {
				String v = getStringAt(i).toLowerCase();

				if (v.length() > 0 && v.charAt(0) == aKey) {
					return i;
				}
			}

			return -1;
		}

		@Override
		public void updateUI() {
			Long l = (Long) UIManager.get("ComboBox.timeFactor");
			timeFactor = l == null ? 1000L : l.longValue();
		}
	}

	private ComboBoxAdapter dataAdapter;

	private DelegatingRenderer delegatingRenderer;

	private StringValueRegistry stringValueRegistry;

	private boolean useHighlightersForCurrentValue = true;

	private CompoundHighlighter compoundHighlighter;

	private ChangeListener highlighterChangeListener;

	private List<KeyEvent> pendingEvents;

	private boolean isDispatching;

	private boolean updatingUI;

	/**
	 * Creates a <code>JXComboBox</code> with a default data model. The default
	 * data model is an empty list of objects. Use <code>addItem</code> to add
	 * items. By default the first item in the data model becomes selected.
	 * 
	 * @see DefaultComboBoxModel
	 */
	public JXComboBox() {
		super();
		init();
	}

	/**
	 * Creates a <code>JXComboBox</code> that takes its items from an existing
	 * <code>ComboBoxModel</code>. Since the <code>ComboBoxModel</code> is
	 * provided, a combo box created using this constructor does not create a
	 * default combo box model and may impact how the insert, remove and add
	 * methods behave.
	 * 
	 * @param model
	 *            the <code>ComboBoxModel</code> that provides the displayed
	 *            list of items
	 * @see DefaultComboBoxModel
	 */
	public JXComboBox(ComboBoxModel model) {
		super(model);
		init();
	}

	/**
	 * Creates a <code>JXComboBox</code> that contains the elements in the
	 * specified array. By default the first item in the array (and therefore
	 * the data model) becomes selected.
	 * 
	 * @param items
	 *            an array of objects to insert into the combo box
	 * @see DefaultComboBoxModel
	 */
	public JXComboBox(Object[] items) {
		super(items);
		init();
	}

	/**
	 * Creates a <code>JXComboBox</code> that contains the elements in the
	 * specified Vector. By default the first item in the vector (and therefore
	 * the data model) becomes selected.
	 * 
	 * @param items
	 *            an array of vectors to insert into the combo box
	 * @see DefaultComboBoxModel
	 */
	public JXComboBox(Vector<?> items) {
		super(items);
		init();
	}

	private void init() {
		pendingEvents = new ArrayList<KeyEvent>();

		if (keySelectionManager == null || keySelectionManager instanceof UIResource) {
			setKeySelectionManager(createDefaultKeySelectionManager());
		}
	}

	protected static JList getPopupListFor(JComboBox comboBox) {
		int count = comboBox.getUI().getAccessibleChildrenCount(comboBox);

		for (int i = 0; i < count; i++) {
			Accessible a = comboBox.getUI().getAccessibleChild(comboBox, i);

			if (a instanceof ComboPopup) {
				return ((ComboPopup) a).getList();
			}
		}

		return null;
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * This implementation uses the {@code StringValue} representation of the
	 * elements to determine the selected item.
	 */
	@Override
	protected KeySelectionManager createDefaultKeySelectionManager() {
		return new StringValueKeySelectionManager();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean processKeyBinding(KeyStroke ks, final KeyEvent e, int condition, boolean pressed) {
		boolean retValue = super.processKeyBinding(ks, e, condition, pressed);

		if (!retValue && editor != null) {
			if (isStartingCellEdit(e)) {
				pendingEvents.add(e);
			} else if (pendingEvents.size() == 2) {
				pendingEvents.add(e);
				isDispatching = true;

				SwingUtilities.invokeLater(new Runnable() {
					@Override
					public void run() {
						try {
							for (KeyEvent event : pendingEvents) {
								editor.getEditorComponent().dispatchEvent(event);
							}

							pendingEvents.clear();
						} finally {
							isDispatching = false;
						}
					}
				});
			}
		}
		return retValue;
	}

	private boolean isStartingCellEdit(KeyEvent e) {
		if (isDispatching) {
			return false;
		}

		JTable table = (JTable) SwingUtilities.getAncestorOfClass(JTable.class, this);
		boolean isOwned = table != null && !Boolean.FALSE.equals(table.getClientProperty("JTable.autoStartsEdit"));

		return isOwned && e.getComponent() == table;
	}

	/**
	 * @return the unconfigured ComponentAdapter.
	 */
	protected ComponentAdapter getComponentAdapter() {
		if (dataAdapter == null) {
			dataAdapter = new ComboBoxAdapter(this);
		}
		return dataAdapter;
	}

	/**
	 * Convenience to access a configured ComponentAdapter. Note: the column
	 * index of the configured adapter is always 0.
	 * 
	 * @param index
	 *            the row index in view coordinates, must be valid.
	 * @return the configured ComponentAdapter.
	 */
	protected ComponentAdapter getComponentAdapter(int index) {
		ComponentAdapter adapter = getComponentAdapter();
		adapter.column = 0;
		adapter.row = index;
		return adapter;
	}

	/**
	 * Returns the StringValueRegistry which defines the string representation
	 * for each cells. This is strictly for internal use by the table, which has
	 * the responsibility to keep in synch with registered renderers.
	 * <p>
	 * 
	 * Currently exposed for testing reasons, client code is recommended to not
	 * use nor override.
	 * 
	 * @return the current string value registry
	 */
	protected StringValueRegistry getStringValueRegistry() {
		if (stringValueRegistry == null) {
			stringValueRegistry = createDefaultStringValueRegistry();
		}
		return stringValueRegistry;
	}

	/**
	 * Creates and returns the default registry for StringValues.
	 * <p>
	 * 
	 * @return the default registry for StringValues.
	 */
	protected StringValueRegistry createDefaultStringValueRegistry() {
		return new StringValueRegistry();
	}

	/**
	 * Returns the string representation of the cell value at the given
	 * position.
	 * 
	 * @param row
	 *            the row index of the cell in view coordinates
	 * @return the string representation of the cell value as it will appear in
	 *         the table.
	 */
	public String getStringAt(int row) {
		// changed implementation to use StringValueRegistry
		StringValue stringValue = getStringValueRegistry().getStringValue(row, 0);

		return stringValue.getString(getItemAt(row));
	}

	private DelegatingRenderer getDelegatingRenderer() {
		if (delegatingRenderer == null) {
			// only called once... to get hold of the default?
			delegatingRenderer = new DelegatingRenderer();
		}
		return delegatingRenderer;
	}

	/**
	 * Creates and returns the default cell renderer to use. Subclasses may
	 * override to use a different type. Here: returns a
	 * <code>DefaultListRenderer</code>.
	 * 
	 * @return the default cell renderer to use with this list.
	 */
	protected ListCellRenderer createDefaultCellRenderer() {
		return new DefaultListRenderer();
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * 
	 * Overridden to return the delegating renderer which is wrapped around the
	 * original to support highlighting. The returned renderer is of type
	 * DelegatingRenderer and guaranteed to not-null
	 * <p>
	 * 
	 * @see #setRenderer(ListCellRenderer)
	 * @see DelegatingRenderer
	 */
	@Override
	public ListCellRenderer getRenderer() {
		// PENDING JW: something wrong here - why exactly can't we return super?
		// not even if we force the initial setting in init?
		// return super.getCellRenderer();
		return getDelegatingRenderer();
	}

	/**
	 * Returns the renderer installed by client code or the default if none has
	 * been set.
	 * 
	 * @return the wrapped renderer.
	 * @see #setRenderer(ListCellRenderer)
	 */
	public ListCellRenderer getWrappedRenderer() {
		return getDelegatingRenderer().getDelegateRenderer();
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * 
	 * Overridden to wrap the given renderer in a DelegatingRenderer to support
	 * highlighting.
	 * <p>
	 * 
	 * Note: the wrapping implies that the renderer returned from the
	 * getCellRenderer is <b>not</b> the renderer as given here, but the
	 * wrapper. To access the original, use <code>getWrappedCellRenderer</code>.
	 * 
	 * @see #getWrappedRenderer()
	 * @see #getRenderer()
	 */
	@Override
	public void setRenderer(ListCellRenderer renderer) {
		// PENDING: do something against recursive setting
		// == multiple delegation...
		ListCellRenderer oldValue = super.getRenderer();
		getDelegatingRenderer().setDelegateRenderer(renderer);
		getStringValueRegistry().setStringValue(renderer instanceof StringValue ? (StringValue) renderer : null, 0);
		super.setRenderer(delegatingRenderer);

		if (oldValue == delegatingRenderer) {
			firePropertyChange("renderer", null, delegatingRenderer);
		}
	}

	/**
	 * PENDING JW to KS: review method naming - doesn't sound like valid English
	 * to me (no native speaker of course :-). Options are to change the
	 * property name to usingHighlightersForCurrentValue (as we did in
	 * JXMonthView after some debate) or stick to getXX. Thinking about it:
	 * maybe then the property should be usesHighlightersXX, that is third
	 * person singular instead of imperative, like in tracksVerticalViewport of
	 * JTable?
	 * 
	 * @return {@code true} if the combo box decorates the current value with
	 *         highlighters; {@code false} otherwise
	 */
	public boolean isUseHighlightersForCurrentValue() {
		return useHighlightersForCurrentValue;
	}

	public void setUseHighlightersForCurrentValue(boolean useHighlightersForCurrentValue) {
		boolean oldValue = isUseHighlightersForCurrentValue();
		this.useHighlightersForCurrentValue = useHighlightersForCurrentValue;
		repaint();
		firePropertyChange("useHighlightersForCurrentValue", oldValue, isUseHighlightersForCurrentValue());
	}

	/**
	 * Returns the CompoundHighlighter assigned to the table, null if none.
	 * PENDING: open up for subclasses again?.
	 * 
	 * @return the CompoundHighlighter assigned to the table.
	 * @see #setCompoundHighlighter(CompoundHighlighter)
	 */
	private CompoundHighlighter getCompoundHighlighter() {
		return compoundHighlighter;
	}

	/**
	 * Assigns a CompoundHighlighter to the table, maybe null to remove all
	 * Highlighters.
	 * <p>
	 * 
	 * The default value is <code>null</code>.
	 * <p>
	 * 
	 * PENDING: open up for subclasses again?.
	 * 
	 * @param pipeline
	 *            the CompoundHighlighter to use for renderer decoration.
	 * @see #getCompoundHighlighter()
	 * @see #addHighlighter(Highlighter)
	 * @see #removeHighlighter(Highlighter)
	 * 
	 */
	private void setCompoundHighlighter(CompoundHighlighter pipeline) {
		CompoundHighlighter old = getCompoundHighlighter();
		if (old != null) {
			old.removeChangeListener(getHighlighterChangeListener());
		}
		compoundHighlighter = pipeline;
		if (compoundHighlighter != null) {
			compoundHighlighter.addChangeListener(getHighlighterChangeListener());
		}
		// PENDING: wrong event - the property is either "compoundHighlighter"
		// or "highlighters" with the old/new array as value
		firePropertyChange("highlighters", old, getCompoundHighlighter());
	}

	/**
	 * Sets the <code>Highlighter</code>s to the column, replacing any old
	 * settings. None of the given Highlighters must be null.
	 * <p>
	 * 
	 * @param highlighters
	 *            zero or more not null highlighters to use for renderer
	 *            decoration.
	 * 
	 * @see #getHighlighters()
	 * @see #addHighlighter(Highlighter)
	 * @see #removeHighlighter(Highlighter)
	 * 
	 */
	public void setHighlighters(Highlighter... highlighters) {
		Contract.asNotNull(highlighters, "highlighters cannot be null or contain null");

		CompoundHighlighter pipeline = null;
		if (highlighters.length > 0) {
			pipeline = new CompoundHighlighter(highlighters);
		}

		setCompoundHighlighter(pipeline);
	}

	/**
	 * Returns the <code>Highlighter</code>s used by this column. Maybe empty,
	 * but guarantees to be never null.
	 * 
	 * @return the Highlighters used by this column, guaranteed to never null.
	 * @see #setHighlighters(Highlighter[])
	 */
	public Highlighter[] getHighlighters() {
		return getCompoundHighlighter() != null ? getCompoundHighlighter().getHighlighters()
				: CompoundHighlighter.EMPTY_HIGHLIGHTERS;
	}

	/**
	 * Adds a Highlighter. Appends to the end of the list of used Highlighters.
	 * <p>
	 * 
	 * @param highlighter
	 *            the <code>Highlighter</code> to add.
	 * @throws NullPointerException
	 *             if <code>Highlighter</code> is null.
	 * 
	 * @see #removeHighlighter(Highlighter)
	 * @see #setHighlighters(Highlighter[])
	 */
	public void addHighlighter(Highlighter highlighter) {
		CompoundHighlighter pipeline = getCompoundHighlighter();
		if (pipeline == null) {
			setCompoundHighlighter(new CompoundHighlighter(highlighter));
		} else {
			pipeline.addHighlighter(highlighter);
		}
	}

	/**
	 * Removes the given Highlighter.
	 * <p>
	 * 
	 * Does nothing if the Highlighter is not contained.
	 * 
	 * @param highlighter
	 *            the Highlighter to remove.
	 * @see #addHighlighter(Highlighter)
	 * @see #setHighlighters(Highlighter...)
	 */
	public void removeHighlighter(Highlighter highlighter) {
		if ((getCompoundHighlighter() == null)) {
			return;
		}
		getCompoundHighlighter().removeHighlighter(highlighter);
	}

	/**
	 * Returns the <code>ChangeListener</code> to use with highlighters. Lazily
	 * creates the listener.
	 * 
	 * @return the ChangeListener for observing changes of highlighters,
	 *         guaranteed to be <code>not-null</code>
	 */
	protected ChangeListener getHighlighterChangeListener() {
		if (highlighterChangeListener == null) {
			highlighterChangeListener = createHighlighterChangeListener();
		}

		return highlighterChangeListener;
	}

	/**
	 * Creates and returns the ChangeListener observing Highlighters.
	 * <p>
	 * A property change event is create for a state change.
	 * 
	 * @return the ChangeListener defining the reaction to changes of
	 *         highlighters.
	 */
	protected ChangeListener createHighlighterChangeListener() {
		return new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				// need to fire change so JXComboBox can update
				firePropertyChange("highlighters", null, getHighlighters());
				repaint();
			}
		};
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * Overridden to update renderer and highlighters.
	 */
	@Override
	public void updateUI() {
		updatingUI = true;

		try {
			super.updateUI();

			if (keySelectionManager instanceof UIDependent) {
				((UIDependent) keySelectionManager).updateUI();
			}

			ListCellRenderer renderer = getRenderer();

			if (renderer instanceof UIDependent) {
				((UIDependent) renderer).updateUI();
			} else if (renderer instanceof Component) {
				SwingUtilities.updateComponentTreeUI((Component) renderer);
			}

			if (compoundHighlighter != null) {
				compoundHighlighter.updateUI();
			}
		} finally {
			updatingUI = false;
		}
	}
}
